package midi;

import data.UserPreferences;

import java.util.ArrayList;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Transmitter;


/**
 * Class: MidiController
 * Description: Receives MidiMessages, decrypts them, and sends the
 * decrypted messages to the observers (uses Observer Pattern).
 * It would be of interest to anyone reading this code to look at
 * http://java.sun.com/j2se/1.5.0/docs/api/javax/sound/midi/MidiMessage.html
 * and
 * http://java.sun.com/j2se/1.5.0/docs/api/javax/sound/midi/ShortMessage.html
 * to have an idea of what's going on.
 * @author et
 */
public class MidiController implements MidiSubject
{
	private ArrayList <MidiObserver> observers;
	
	private ShortMessage message;
	private int assignment;
	private int velocity;

    UserPreferences up = UserPreferences.getInstance();

    /**
     * Constructor for MidiController.
     */
	public MidiController()
	{
		observers = new ArrayList <MidiObserver>();
	}


    /**
     * Refreshes the MIDI controller.
     */
    public void refreshDevice()
    {
        observers = new ArrayList <MidiObserver>();
        if (!up.getMidiIn().equals(UserPreferences.NULL))
        {

            MidiDevice.Info info = MidiHelpers.getMidiDeviceInfo(up.getMidiIn(), false);

            MidiDevice inputDevice = null;
            try
            {
                inputDevice = MidiSystem.getMidiDevice(info);
                inputDevice.open();

                Receiver receiver = this;
                try
                {
                    Transmitter trasmitter = inputDevice.getTransmitter();
                    trasmitter.setReceiver(receiver);
                }
                catch (MidiUnavailableException e)
                {
                    System.out.println("Could not open MIDI device.");
                    System.err.println(e);
                    inputDevice.close();
                    System.exit(1);
                }
            }
            catch (MidiUnavailableException e)
            {
                System.out.println("Invalid MIDI device in.");
                System.err.println("e");
            }
        }
    }

    /**
     * Regists an observer to listen to this device.
     * @param o - the new MidiObserver
     */
	public void registerObserver(MidiObserver o)
	{
		observers.add(o);
	}

    /**
     * Removes an observer from listening to the device.
     * @param o - the MidiObserver to remove.
     */
	public void removeObserver(MidiObserver o)
	{
		int i = observers.indexOf(o);
		if (i >= 0)
		{
			observers.remove(i);
		}
	}

    /**
     * Sends to all observers that a note on
     * was played.
     */
	public void notifyObserversHitOn()
	{
        for (MidiObserver o : observers)
		{
			o.hitOn(assignment, velocity);
		}
        
	}

    /**
     * Sends to all observers that a note off
     * was played.
     */
    public void notifyObserversHitOff()
	{
		for (MidiObserver o : observers)
		{
			o.hitOff(assignment, velocity);
		}
	}

    /**
     * Sends to all observers that a polyphonic aftertouch
     * was played.
     */
    public void notifyObserversTouch()
	{
		for (MidiObserver o : observers)
		{
			o.touch(assignment, velocity);
		}
	}
	

    /**
     * Required by the Receiver interface,
     * I need to implement this.
     */
	public void close()
    {
	}


    /**
     * Required by the Receiver interface.
     * Receives a new MIDI message and the time it was sent.
     * @param midiMessage - the MIDI message
     * @param timeStamp - the time it was sent.
     */
	public void send(MidiMessage midiMessage, long timeStamp) 
	{
        

		this.message = (ShortMessage) midiMessage;
		
		
		switch (message.getCommand())
		{
		    // Note on
            case ShortMessage.NOTE_ON: 
                assignment = message.getData1();
                velocity = message.getData2();
                notifyObserversHitOn();
                break;

            // Note off
            case ShortMessage.NOTE_OFF:
                assignment = message.getData1();
                velocity = message.getData2();
                notifyObserversHitOff();
                break;

            // Polyphonic aftertouch
            case ShortMessage.POLY_PRESSURE:
                assignment = message.getData1();
                velocity = message.getData2();
                notifyObserversTouch();
                break;


			
		default:
			break;
			
		}
	}
	
}
